#!/bin/bash

# 个性化 WebHook 管理器 v3.0
# 支持多任务、自定义传参(URL变量参数、位置参数、Git平台数据)
# 作者：宋利军
# 版本：3.0

# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m'

# 目录结构
BASE_DIR="/etc/webhook_tool"
TASKS_DIR="$BASE_DIR/tasks"
SCRIPTS_DIR="$BASE_DIR/scripts"
PID_DIR="$BASE_DIR/pids"
LOG_DIR="/var/log/webhook_tool"
SYSTEMD_DIR="/etc/systemd/system"
GLOBAL_CONF="$BASE_DIR/tool.conf"

mkdir -p "$TASKS_DIR" "$SCRIPTS_DIR" "$PID_DIR" "$LOG_DIR"

# 依赖检查
check_dependencies() {
  local missing=()
  command -v python3 >/dev/null 2>&1 || missing+=("python3")
  command -v curl >/dev/null 2>&1 || missing+=("curl")
  if ! command -v ss >/dev/null 2>&1 && ! command -v netstat >/dev/null 2>&1 && ! command -v lsof >/dev/null 2>&1; then
    missing+=("ss或netstat或lsof")
  fi
  if [ ${#missing[@]} -gt 0 ]; then
    echo -e "${YELLOW}⚠️ 缺少依赖: ${missing[*]}${NC}"
    if command -v yum >/dev/null 2>&1; then
      yum install -y python3 curl net-tools lsof >/dev/null 2>&1 || true
    elif command -v apt-get >/dev/null 2>&1; then
      apt-get update >/dev/null 2>&1 && apt-get install -y python3 curl net-tools lsof >/dev/null 2>&1 || true
    fi
  fi
  
  # 检查timeout命令
  if ! command -v timeout >/dev/null 2>&1; then
    echo -e "${YELLOW}⚠️ 未检测到timeout命令，端口检测可能较慢${NC}"
  fi
}

# 工具函数
ensure_global_port() {
  local start_port=9000
  # 若已存在全局端口配置，直接复用并提示
  if [ -f "$GLOBAL_CONF" ]; then
    source "$GLOBAL_CONF"
    if [[ "$GLOBAL_PORT" =~ ^[0-9]+$ ]]; then
      echo -e "${GREEN}✅ 使用 WebHook 全局端口 ${GLOBAL_PORT}${NC}" 1>&2
      echo "$GLOBAL_PORT"
      return
    fi
  fi

  echo -e "${CYAN}正在自动分配 WebHook 全局端口...${NC}" 1>&2
  local chosen=""
  local candidates=()
  for p in $(seq $start_port $((start_port+20))); do
    if ! is_port_in_use "$p"; then
      candidates+=("$p")
      [ ${#candidates[@]} -ge 3 ] && break
    fi
  done
  if [ ${#candidates[@]} -eq 0 ]; then
    # 若未找到，直接使用起始端口
    chosen=$start_port
  else
    echo -e "${GREEN}✅ 发现可用端口: ${candidates[*]}${NC}" 1>&2
    chosen=${candidates[0]}
  fi

  echo -e "${GREEN}✅ 端口 ${chosen} 可用，已经全局配置${NC}" 1>&2
  echo "GLOBAL_PORT=\"$chosen\"" > "$GLOBAL_CONF"
  echo "$chosen"
}

get_server_ip() {
  local ip
  ip=$(curl -s ipinfo.io/ip 2>/dev/null)
  [ -z "$ip" ] && ip=$(hostname -I 2>/dev/null | awk '{print $1}')
  [ -z "$ip" ] && ip="127.0.0.1"
  echo "$ip"
}

is_port_in_use() {
  local port="$1"
  local result=1
  
  # 使用timeout防止长时间等待
  if command -v timeout >/dev/null 2>&1; then
    if command -v ss >/dev/null 2>&1; then
      timeout 2s ss -tln 2>/dev/null | awk '{print $4}' | grep -E ":$port$" -q && result=0
    elif command -v netstat >/dev/null 2>&1; then
      timeout 2s netstat -tln 2>/dev/null | awk '{print $4}' | grep -E ":$port$" -q && result=0
    elif command -v lsof >/dev/null 2>&1; then
      timeout 2s lsof -iTCP -sTCP:LISTEN -P 2>/dev/null | awk '{print $9}' | grep -E ":$port$" -q && result=0
    fi
  else
    # 没有timeout命令时的备用方案
    if command -v ss >/dev/null 2>&1; then
      ss -tln 2>/dev/null | awk '{print $4}' | grep -E ":$port$" -q && result=0
    elif command -v netstat >/dev/null 2>&1; then
      netstat -tln 2>/dev/null | awk '{print $4}' | grep -E ":$port$" -q && result=0
    elif command -v lsof >/dev/null 2>&1; then
      lsof -iTCP -sTCP:LISTEN -P 2>/dev/null | awk '{print $9}' | grep -E ":$port$" -q && result=0
    fi
  fi
  
  return $result
}

generate_token() {
  if command -v openssl >/dev/null 2>&1; then
    openssl rand -hex 12
  else
    echo "$RANDOM$(date +%s%N)$RANDOM" | md5sum | awk '{print $1}'
  fi
}

read_multiline_commands() {
  local end_marker="$1"
  local content=""
  
  # 显示输入说明（输出到stderr，避免混入脚本内容）
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 1>&2
  echo -e "${WHITE}📋 输入脚本内容${NC}" 1>&2
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 1>&2
  echo -e "${YELLOW}📝 参数传递方式说明：${NC}" 1>&2
  echo -e "  ${WHITE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 1>&2
  echo -e "  ${WHITE}方式1：URL查询参数（通过环境变量获取）～${NC}" 1>&2
  echo -e "  ${WHITE}  • 语法：http://IP:端口/hooks/TOKEN?name=值&age=值&sex=值～${NC}" 1>&2
  echo -e "  ${WHITE}  • 获取：在脚本中直接使用 ${CYAN}\$name${NC}、${CYAN}\$age${NC}、${CYAN}\$sex${NC} 获取～${NC}" 1>&2
  echo -e "  ${WHITE}  • 示例：?name=宋利军&age=30&sex=男 可通过 ${CYAN}\$name${NC}、${CYAN}\$age${NC}、${CYAN}\$sex${NC} 获取～${NC}" 1>&2
  echo -e "  ${WHITE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 1>&2
  echo -e "  ${WHITE}方式2：位置参数（通过\$1、\$2、\$3...获取）～${NC}" 1>&2
  echo -e "  ${WHITE}  • 语法：URL参数值按顺序传递给脚本的位置参数～${NC}" 1>&2
  echo -e "  ${WHITE}  • 获取：在脚本中使用 ${CYAN}\$1${NC}、${CYAN}\$2${NC}、${CYAN}\$3${NC} 按顺序获取～${NC}" 1>&2
  echo -e "  ${WHITE}  • 示例：?name=宋利军&age=30&sex=男 对应 ${CYAN}\$1=宋利军${NC}、${CYAN}\$2=30${NC}、${CYAN}\$3=男${NC}～${NC}" 1>&2
  echo -e "  ${WHITE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 1>&2
  echo -e "  ${WHITE}方式3：Git平台数据（通过GIT_前缀环境变量获取）～${NC}" 1>&2
  echo -e "  ${WHITE}  • 支持：GitHub、GitLab、码云、工蜂Git等主流平台～${NC}" 1>&2
  echo -e "  ${WHITE}  • 示例：${NC}" 1>&2  
  echo -e "  ${WHITE}       echo \"Git平台信息：\"${NC}" 1>&2
  echo -e "  ${WHITE}       echo \"  平台类型: \${GIT_PLATFORM:-未设置}\"${NC}" 1>&2
  echo -e "  ${WHITE}       echo \"  仓库名称: \${GIT_REPO_NAME:-未设置}\"${NC}" 1>&2
  echo -e "  ${WHITE}       echo \"  仓库地址: \${GIT_REPO_URL:-未设置}\"${NC}" 1>&2
  echo -e "  ${WHITE}       echo \"  分支名称: \${GIT_BRANCH:-未设置}\"${NC}" 1>&2
  echo -e "  ${WHITE}       echo \"  提交ID: \${GIT_COMMIT_ID:-未设置}\"${NC}" 1>&2
  echo -e "  ${WHITE}       echo \"  提交信息: \${GIT_COMMIT_MESSAGE:-未设置}\"${NC}" 1>&2
  echo -e "  ${WHITE}       echo \"  作者: \${GIT_AUTHOR:-未设置}\"${NC}" 1>&2
  echo -e "  ${WHITE}       echo \"  提交者: \${GIT_COMMITTER:-未设置}\"${NC}" 1>&2
  echo -e "  ${WHITE}       if [ -n \"\$GIT_FILES_CHANGED\" ]; then${NC}" 1>&2
  echo -e "  ${WHITE}           echo \"  变更文件:\"${NC}" 1>&2
  echo -e "  ${WHITE}           echo -e \"\$GIT_FILES_CHANGED\" | sed 's/^/     /'${NC}" 1>&2
  echo -e "  ${WHITE}       else${NC}" 1>&2
  echo -e "  ${WHITE}           echo \"  变更文件: 未设置\"${NC}" 1>&2
  echo -e "  ${WHITE}       fi${NC}" 1>&2
  echo -e "  ${WHITE}       echo${NC}" 1>&2
  echo -e "  ${WHITE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 1>&2
  echo -e "  ${WHITE}📝 使用说明：${NC}" 1>&2
  echo -e "  ${WHITE}  • 每行代表一条命令，支持多行脚本录入方式，按顺序执行～${NC}" 1>&2
  echo -e "  ${WHITE}  • 输入完成后，单独一行输入 ${CYAN}${end_marker}${NC} 结束，或按 ${CYAN}Ctrl+D${NC} 结束～${NC}" 1>&2
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" 1>&2
  echo -e "${WHITE}开始输入命令（输入 ${end_marker} 结束）：${NC}" 1>&2
  
  # 开始读取用户输入
  while IFS= read -r line; do
    [ "$line" = "$end_marker" ] && break
    content+="$line\n"
  done
  
  printf "%b" "$content"
}

# 生成Python服务脚本
create_python_server() {
  local port="$1"
  local py_file="$SCRIPTS_DIR/main_server.py"
  cat > "$py_file" << PYEOF
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import threading, subprocess, time, os
import sys
import urllib.parse
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn
from urllib.parse import urlparse, parse_qs

# 设置环境变量
os.environ['PYTHONIOENCODING'] = 'utf-8'
os.environ['LANG'] = 'zh_CN.UTF-8'
os.environ['LC_ALL'] = 'zh_CN.UTF-8'

PORT = ${port}
BASE_DIR = r"${BASE_DIR}"
TASKS_DIR = os.path.join(BASE_DIR, 'tasks')
LOG_DIR = r"${LOG_DIR}"
os.makedirs(LOG_DIR, exist_ok=True)

def _safe_get(d: dict, path: list, default=""):
    cur = d
    for key in path:
        if not isinstance(cur, dict):
            return default
        cur = cur.get(key, {})
    return cur if cur != {} else default

def _strip_branch(ref: str) -> str:
    return (ref or "").replace('refs/heads/', '')

def _get_files_changed(commit_data: dict) -> str:
    """从提交数据中提取变更的文件列表
    支持格式：每行一个文件，包含操作类型前缀
    """
    if not isinstance(commit_data, dict):
        return ""
    
    files = []
    
    # 获取新增文件
    added = commit_data.get('added', [])
    if isinstance(added, list) and added:
        files.extend([f"+ {f}" for f in added])
    
    # 获取修改文件
    modified = commit_data.get('modified', [])
    if isinstance(modified, list) and modified:
        files.extend([f"* {f}" for f in modified])
    
    # 获取删除文件
    removed = commit_data.get('removed', [])
    if isinstance(removed, list) and removed:
        files.extend([f"- {f}" for f in removed])
    
    return "\n".join(files) if files else ""

def parse_git_payload(data: dict) -> dict:
    """将 GitHub/GitLab/Gitee/Coding 的 Webhook 负载统一解析为通用字段。
    返回字段：platform, repo_name, repo_url, branch, commit_id, commit_message, author, committer, files_changed
    未获取到的字段返回空字符串。
    """
    if not isinstance(data, dict):
        return {}

    platform = ""
    repo_name = repo_url = branch = commit_id = commit_message = author = committer = ""
    files_changed = ""

    # 首先通过 repository 字段识别平台
    if 'repository' in data:
        repo = data.get('repository') or {}
        repo_name = repo.get('name', '')
        repo_url = repo.get('html_url') or repo.get('url') or repo.get('web_url') or ""
        
        # 通过URL识别平台
        url_l = (repo_url or '').lower()
        if 'github.com' in url_l:
            platform = 'github'
        elif 'gitee.com' in url_l:
            platform = 'gitee'
        elif 'coding.net' in url_l or 'dev.tencent.com' in url_l:
            platform = 'coding'
        elif 'gitlab' in url_l:
            platform = 'gitlab'
        
        # 通过数据结构进一步确认
        if not platform:
            if 'head_commit' in data:
                platform = 'github'  # GitHub特有字段
            elif 'project' in data:
                platform = 'gitlab'  # GitLab特有字段
            elif 'commits' in data and 'repository' in data:
                # 兜底：有 repository + commits，多半是 github 风格
                platform = 'github'

        branch = _strip_branch(data.get('ref', ''))
        commit_id = data.get('after', '') or data.get('head_commit', {}).get('id', '')

        # 获取提交信息
        head_commit = data.get('head_commit')
        if isinstance(head_commit, dict) and head_commit:
            commit_message = head_commit.get('message', '')
            author = _safe_get(head_commit, ['author', 'name']) or _safe_get(head_commit, ['author', 'username'])
            committer = _safe_get(head_commit, ['committer', 'name']) or _safe_get(head_commit, ['committer', 'username'])
            # 获取文件变更信息
            files_changed = _get_files_changed(head_commit)
        else:
            # 从commits数组获取第一个提交
            commits = data.get('commits', [])
            if commits and isinstance(commits, list) and len(commits) > 0:
                first_commit = commits[0]
                commit_message = first_commit.get('message', '')
                author = _safe_get(first_commit, ['author', 'name']) or _safe_get(first_commit, ['author', 'username'])
                committer = _safe_get(first_commit, ['committer', 'name']) or _safe_get(first_commit, ['committer', 'username'])
                # 获取文件变更信息
                files_changed = _get_files_changed(first_commit)
            else:
                # 尝试从其他字段获取
                commit_message = data.get('message', '')
                author = _safe_get(data, ['author', 'name']) or _safe_get(data, ['author', 'username'])
                committer = _safe_get(data, ['committer', 'name']) or _safe_get(data, ['committer', 'username'])
                files_changed = _get_files_changed(data)

        return {
            'platform': platform or 'unknown',
            'repo_name': repo_name,
            'repo_url': repo_url,
            'branch': branch,
            'commit_id': commit_id,
            'commit_message': commit_message,
            'author': author,
            'committer': committer,
            'files_changed': files_changed,
        }

    # GitLab特有结构：使用 project 字段
    elif 'project' in data:
        platform = 'gitlab'
        repo_name = _safe_get(data, ['project', 'name']) or ""
        repo_url = _safe_get(data, ['project', 'web_url']) or _safe_get(data, ['project', 'url']) or ""
        branch = _strip_branch(data.get('ref', ''))
        commit_id = data.get('after', '')
        
        # 获取提交信息
        commits = data.get('commits', [])
        if commits and isinstance(commits, list) and len(commits) > 0:
            first_commit = commits[0]
            commit_message = first_commit.get('message', '')
            author = _safe_get(first_commit, ['author', 'name']) or _safe_get(first_commit, ['author', 'username'])
            committer = _safe_get(first_commit, ['committer', 'name']) or _safe_get(first_commit, ['committer', 'username'])
            # 获取文件变更信息
            files_changed = _get_files_changed(first_commit)
        else:
            commit_message = data.get('message', '')
            author = _safe_get(data, ['author', 'name']) or _safe_get(data, ['author', 'username'])
            committer = _safe_get(data, ['committer', 'name']) or _safe_get(data, ['committer', 'username'])
            files_changed = _get_files_changed(data)

        return {
            'platform': platform,
            'repo_name': repo_name,
            'repo_url': repo_url,
            'branch': branch,
            'commit_id': commit_id,
            'commit_message': commit_message,
            'author': author,
            'committer': committer,
            'files_changed': files_changed,
        }

    # 未识别
    return {}

# 统一的查询参数解析工具
from urllib.parse import unquote_to_bytes

def _decode_bytes_to_str(raw: bytes) -> str:
    try:
        return raw.decode('utf-8')
    except UnicodeDecodeError:
        return raw.decode('latin-1', errors='replace')

def _parse_qbytes_to_dict(qbytes: bytes) -> dict:
    params: dict = {}
    if not qbytes:
        return params
    for pair in qbytes.split(b'&'):
        if not pair:
            continue
        if b'=' in pair:
            k, v = pair.split(b'=', 1)
        else:
            k, v = pair, b''
        k = k.replace(b'+', b' ')
        v = v.replace(b'+', b' ')
        k_b = unquote_to_bytes(k.decode('latin-1', errors='ignore'))
        v_b = unquote_to_bytes(v.decode('latin-1', errors='ignore'))
        k_s = _decode_bytes_to_str(k_b)
        v_s = _decode_bytes_to_str(v_b)
        params[k_s] = v_s
    return params

def _fix_mojibake(params: dict) -> dict:
    def looks_mojibake(s: str) -> bool:
        return any(ch in s for ch in ('å', 'ç', 'é', 'æ', 'Ã', 'Â'))
    fixed = dict(params)
    for k, v in list(fixed.items()):
        if isinstance(v, str) and looks_mojibake(v):
            try:
                cand = v.encode('latin-1', errors='strict').decode('utf-8', errors='strict')
                if any('\u4e00' <= ch <= '\u9fff' for ch in cand):
                    fixed[k] = cand
            except Exception:
                pass
    return fixed

def parse_query_params(self, parsed) -> dict:
    # 优先从原始请求行获取字节
    try:
        raw_line = getattr(self, 'raw_requestline', b'')
        if raw_line:
            parts = raw_line.split(b' ')
            if len(parts) >= 2:
                path_bytes = parts[1]
                qmark = path_bytes.find(b'?')
                if qmark != -1 and qmark + 1 < len(path_bytes):
                    qbytes = path_bytes[qmark+1:]
                    return _fix_mojibake(_parse_qbytes_to_dict(qbytes))
    except Exception:
        pass
    # 回退：使用 parsed.query 再还原字节
    try:
        if parsed.query:
            return _fix_mojibake(_parse_qbytes_to_dict(parsed.query.encode('latin-1', errors='ignore')))
    except Exception:
        pass
    return {}

def run_task(script_path, token, params=None, git_data=None):
    log_path = os.path.join(LOG_DIR, f"task_{token}.log")
    start_ts = time.strftime('%Y-%m-%d %H:%M:%S')
    with open(log_path, 'a', buffering=1, encoding='utf-8') as f:
        f.write(f"[{start_ts}] WebHook触发，开始执行任务，token={token}")
        if params:
            # 正确解码并显示中文参数
            param_list = []
            for key, value in params.items():
                # 确保参数值是字符串（如果是字节串则解码）
                if isinstance(value, bytes):
                    try:
                        value = value.decode('utf-8')
                    except:
                        value = value.decode('latin1')
                param_list.append(f"{key}={value}")
            f.write(f"，URL参数: {', '.join(param_list)}")
        if git_data:
            f.write(f"，Git数据: {git_data}")
        f.write("\n")
        try:
            # 创建临时环境变量文件
            env_file = f"/tmp/webhook_env_{token}_{int(time.time())}.sh"
            with open(env_file, 'w', encoding='utf-8') as env_f:
                env_f.write("#!/bin/bash\n")
                
                # 设置URL查询参数为环境变量（支持$参数名称方式）
                if params:
                    for key, value in params.items():
                        # 确保值是字符串
                        if isinstance(value, bytes):
                            try:
                                value = value.decode('utf-8')
                            except:
                                value = value.decode('latin1')
                        # 对特殊字符进行转义
                        escaped_value = value.replace('"', '\\"')
                        env_f.write(f'export {key}="{escaped_value}"\n')
                
                # 设置位置参数（支持$1、$2、$3...方式）
                # 注意：这里不设置环境变量，让bash通过命令行参数自动设置$1、$2、$3
                # 环境变量方式：$name、$age、$sex
                # 位置参数方式：$1、$2、$3（通过命令行参数传递）
                
                # 设置Git相关环境变量
                if git_data:
                    for key, value in git_data.items():
                        if isinstance(value, bytes):
                            try:
                                value = value.decode('utf-8')
                            except:
                                value = value.decode('latin1')
                        
                        # 特殊处理files_changed字段，确保多行格式正确
                        if key == 'files_changed' and value:
                            # 将多行内容转换为单行，用\n分隔
                            escaped_value = value.replace('"', '\\"').replace('\n', '\\n')
                        else:
                            escaped_value = value.replace('"', '\\"')
                        
                        env_f.write(f'export GIT_{key.upper()}="{escaped_value}"\n')
            
            # 执行脚本，先source环境变量文件，然后传递位置参数
            param_args = []
            if params:
                # 按顺序添加参数值
                for value in params.values():
                    if isinstance(value, bytes):
                        try:
                            value = value.decode('utf-8')
                        except:
                            value = value.decode('latin1')
                    param_args.append(value)
            
            cmd = ['/bin/bash', '-c', f'source {env_file} && {script_path} {" ".join(param_args)}']
            

            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, errors='replace', env=dict(os.environ, PYTHONIOENCODING='utf-8', LANG='zh_CN.UTF-8', LC_ALL='zh_CN.UTF-8'))
            for line in p.stdout:
                f.write(line)
            p.wait()
            
            # 清理临时文件
            try:
                os.unlink(env_file)
            except:
                pass
                
            end_ts = time.strftime('%Y-%m-%d %H:%M:%S')
            f.write(f"[{end_ts}] 任务结束，退出码: {p.returncode}\n")
        except Exception as e:
            f.write(f"[ERROR] 执行异常: {e}\n")

class Handler(BaseHTTPRequestHandler):
    def log_message(self, format, *args):
        return
    def _ok(self, msg):
        data = msg.encode('utf-8')
        self.send_response(200)
        self.send_header('Content-Type', 'text/plain; charset=utf-8')
        self.send_header('Content-Length', str(len(data)))
        self.send_header('Connection', 'close')
        self.end_headers()
        try:
            self.wfile.write(data)
        except Exception:
            pass
    def _not_found(self):
        data = b'Not Found'
        self.send_response(404)
        self.send_header('Content-Type', 'text/plain; charset=utf-8')
        self.send_header('Content-Length', str(len(data)))
        self.send_header('Connection', 'close')
        self.end_headers()
        try:
            self.wfile.write(data)
        except Exception:
            pass
    def do_GET(self):
        # GET请求也需要解析URL参数
        self._handle({})
    def do_POST(self):
        # 读取请求体，解析Git平台数据
        git_data = {}
        try:
            length = int(self.headers.get('Content-Length', '0'))
            if length > 0:
                import json
                body = self.rfile.read(length).decode('utf-8')
                try:
                    data = json.loads(body)
                    parsed = parse_git_payload(data)
                    if parsed:
                        git_data.update(parsed)
                except json.JSONDecodeError:
                    pass
        except Exception:
            pass
        self._handle(git_data)
    def _handle(self, git_data=None):
        parsed = urlparse(self.path)
        path = parsed.path
        
        # 统一解析
        params = parse_query_params(self, parsed)

        # /hooks/<token>
        parts = [p for p in path.split('/') if p]
        if len(parts) == 2 and parts[0] == 'hooks':
            token = parts[1]
            # 遍历按任务ID命名的配置，匹配 TOKEN
            try:
                for name in os.listdir(TASKS_DIR):
                    if not (name.startswith('task_') and name.endswith('.conf')):
                        continue
                    # 仅匹配 task_<数字>.conf
                    id_part = name[5:-5]
                    if not id_part.isdigit():
                        continue
                    conf_path = os.path.join(TASKS_DIR, name)
                    task_token = None
                    script_path = None
                    with open(conf_path, 'r') as cf:
                        for line in cf:
                            line = line.strip()
                            if line.startswith('TOKEN='):
                                task_token = line.split('=',1)[1].strip().strip('"')
                            elif line.startswith('SCRIPT_PATH='):
                                script_path = line.split('=',1)[1].strip().strip('"')
                    if task_token == token and script_path and os.path.isfile(script_path):
                        threading.Thread(target=run_task, args=(script_path, token, params, git_data), daemon=True).start()
                        response = 'OK: task started'
                        if params:
                            # 显示参数（确保中文正确）
                            param_list = []
                            for key, value in params.items():
                                param_list.append(f"{key}={value}")
                            response += f' with URL params: {", ".join(param_list)}'
                        if git_data:
                            response += f' with Git data: {git_data.get("platform", "unknown")}'
                        response += '\n'
                        self._ok(response)
                        return
            except Exception:
                pass
            self._not_found()
        elif path == "/healthz":
            self._ok('ok')
        else:
            self._not_found()

class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
    daemon_threads = True

if __name__ == '__main__':
    httpd = ThreadingHTTPServer(('0.0.0.0', PORT), Handler)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
PYEOF
  chmod +x "$py_file"
}

# 创建 systemd 服务
create_systemd_service() {
  local py_file="$SCRIPTS_DIR/main_server.py"
  local svc="webhook_tool.service"
  cat > "$SYSTEMD_DIR/$svc" << SVC
[Unit]
Description=Webhook Tool Main Server
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 ${py_file}
Restart=always
RestartSec=3
User=root

[Install]
WantedBy=multi-user.target
SVC
  systemctl daemon-reload 2>/dev/null || true
}

start_main_service() {
  local port="$1"
  local py_file="$SCRIPTS_DIR/main_server.py"
  
  if command -v systemctl >/dev/null 2>&1; then
    echo -e "${YELLOW}  使用systemd启动服务...${NC}"
    systemctl enable --now "webhook_tool.service" >/dev/null 2>&1 || systemctl start "webhook_tool.service" >/dev/null 2>&1
  else
    echo -e "${YELLOW}  使用后台进程启动服务...${NC}"
    nohup python3 "$py_file" > "$LOG_DIR/main_server.log" 2>&1 &
    echo $! > "$PID_DIR/main_server.pid"
  fi
  
  echo -e "${YELLOW}  等待服务启动...${NC}"
  sleep 1.5
  
  echo -e "${YELLOW}  检查服务状态...${NC}"
  if is_port_in_use "$port"; then
    echo -e "${GREEN}✅ 服务已启动${NC}"
  else
    echo -e "${RED}❌ 服务启动失败，请检查日志: $LOG_DIR/main_server.log${NC}"
  fi
}

stop_task_service() { :; }

# 任务管理
add_webhook_task() {
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}➕ 新增 WebHook 任务${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  while true; do
    read -p "任务名称: " task_name
    echo

    [ -z "$task_name" ] && echo -e "${RED}❌ 任务名称不能为空${NC}" && continue

    # 检查任务名称是否已存在
    local name_exists=false
    for conf in "$TASKS_DIR"/*.conf; do
      [ -f "$conf" ] || continue
      source "$conf" 2>/dev/null || continue
      if [ "$TASK_NAME" = "$task_name" ]; then
        echo -e "${RED}❌ 任务名称 '$task_name' 已存在，请重新输入${NC}"
        name_exists=true
        break
      fi
    done
    
    [ "$name_exists" = false ] && break
  done

  # 共用端口由全局配置管理，此处不再逐任务设置端口

  local task_id=$(date +%s)
  local token=$(generate_token)
  local script_path="$TASKS_DIR/task_${task_id}.sh"
  local log_path="$LOG_DIR/task_${token}.log"
  local conf_path="$TASKS_DIR/task_${task_id}.conf"
  
  local cmds
  cmds=$(read_multiline_commands "END")
  if [ -z "$cmds" ]; then
    echo -e "${YELLOW}⚠️ 未输入任务内容，已取消${NC}"; return
  fi

  # 仅规范换行（移除Windows回车），不做其它清理，确保多行脚本逐行保留
  local clean_cmds
  clean_cmds=$(printf '%s' "$cmds" | tr -d '\r')
  
  printf "#!/bin/bash\nset -e\n\n# 多参数模式：支持三种参数获取方式～\n# 方式1：URL查询参数自动转为环境变量，例如：?name=宋利军&age=30&sex=男 可通过 \$name、\$age、\$sex 获取～\n# 方式2：位置参数 \$1、\$2、\$3... 按参数顺序获取～\n# 方式3：Git平台数据：通过 \$GIT_PLATFORM、\$GIT_REPO_NAME、\$GIT_BRANCH、\$GIT_COMMIT_ID、\$GIT_COMMIT_MESSAGE、\$GIT_AUTHOR 等获取～\n\n%s\n" "$clean_cmds" > "$script_path"
  # 赋予脚本所属与权限，确保主服务可执行
  chown root:root "$script_path" 2>/dev/null || true
  chmod +x "$script_path"
  
  # 验证生成的脚本是否有效
  if [ ! -s "$script_path" ]; then
    echo -e "${RED}❌ 生成的脚本为空，请重新输入任务内容${NC}"
    return 1
  fi
  
  # 检查脚本语法（如果bash可用）
  if command -v bash >/dev/null 2>&1; then
    if ! bash -n "$script_path" 2>/dev/null; then
      echo -e "${YELLOW}⚠️ 脚本语法检查失败，但将继续创建任务${NC}"
    fi
  fi

  # 保存配置（将日志路径与Python主服务一致，按token命名）
  cat > "$conf_path" << EOF
TASK_ID="$task_id"
TASK_NAME="$task_name"
PORT="$global_port"
TOKEN="$token"
SCRIPT_PATH="$script_path"
LOG_PATH="$LOG_DIR/task_${token}.log"
PY_FILE="$SCRIPTS_DIR/main_server.py"
EOF

  # 不再创建 token 命名的配置副本，避免 UI/存储上产生重复项

  echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}🚀 部署服务${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  
  echo -e "${YELLOW}正在自动分配 WebHook 全局端口...${NC}"
  local global_port=$(ensure_global_port)
  echo
  
  echo -e "${YELLOW}正在生成/更新主服务脚本...${NC}"
  create_python_server "$global_port"
  
  if command -v systemctl >/dev/null 2>&1; then
    echo -e "${YELLOW}正在创建/更新 systemd 主服务...${NC}"
    create_systemd_service
  else
    echo -e "${YELLOW}未检测到systemd，将使用后台进程模式...${NC}"
  fi
  
  echo -e "${YELLOW}正在启动主服务...${NC}"
  start_main_service "$global_port"

  local ip=$(get_server_ip)
  echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}🎉 任务创建成功${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${GREEN}✅ 任务创建成功${NC}"
  echo -e "${CYAN}WebHook 地址: ${WHITE}http://$ip:${global_port}/hooks/${token}${NC}"
  echo -e "${CYAN}日志文件: ${WHITE}$log_path${NC}"
  echo -e "${CYAN}无参测试: ${WHITE}curl -s -X POST 'http://$ip:${global_port}/hooks/${token}'${NC}"
  echo -e "${CYAN}带参测试(方式1):${NC}"
  echo -e "  ${WHITE}curl -s -G 'http://$ip:${global_port}/hooks/${token}' \\"
  echo -e "    --data-urlencode \"name=宋利军\" \\"
  echo -e "    --data-urlencode \"age=30\" \\"
  echo -e "    --data-urlencode \"sex=男\"${NC}"
  echo -e "${CYAN}带参测试(方式2): ${WHITE}curl -s -X POST 'http://$ip:${global_port}/hooks/${token}?name=宋利军&age=30&sex=男'${NC}"
  echo -e "${CYAN}测试Git Push推送(方式3): 复制网址[${WHITE}http://$ip:${global_port}/hooks/${token}${NC}${CYAN}]到Git服务商WebHook地址项填写${NC}"
  echo -e "${CYAN}查看脚本: ${WHITE}主菜单 -> 查看 WebHook 脚本${NC}"
}

list_webhook_tasks() {
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}📋 WebHook 任务列表（含任务脚本路径）${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  local any=0
  for conf in "$TASKS_DIR"/*.conf; do
    [ -f "$conf" ] || continue
    # 仅展示按任务ID命名的配置，忽略按token命名的别名配置
    base=$(basename "$conf")
    if ! [[ "$base" =~ ^task_[0-9]+\.conf$ ]]; then
      continue
    fi
    any=1
    source "$conf"
    local status="未运行"
    local gp=""
    if [ -f "$GLOBAL_CONF" ]; then source "$GLOBAL_CONF"; gp="$GLOBAL_PORT"; fi
    is_port_in_use "${gp:-$PORT}" && status="运行中"
    echo -e "${WHITE}任务: ${TASK_NAME}${NC}"
    echo -e "  ID: ${CYAN}${TASK_ID}${NC}"
    echo -e "  共用端口: ${CYAN}${gp:-$PORT}${NC}    状态: ${CYAN}${status}${NC}"
    echo -e "  无参地址: ${CYAN}http://$(get_server_ip):${gp:-$PORT}/hooks/${TOKEN}${NC}"
    echo -e "  有参地址(方式1): ${CYAN}curl -s -G 'http://$(get_server_ip):${gp:-$PORT}/hooks/${TOKEN}' \\"
    echo -e "    --data-urlencode \"name=宋利军\" \\"
    echo -e "    --data-urlencode \"age=30\" \\"
    echo -e "    --data-urlencode \"sex=男\"${NC}"
    echo -e "  有参地址(方式2): ${CYAN}http://$(get_server_ip):${gp:-$PORT}/hooks/${TOKEN}?name=宋利军&age=30&sex=男${NC}"
    echo -e "  任务配置: ${CYAN}${conf}${NC}"
    echo -e "  任务脚本: ${CYAN}${SCRIPT_PATH}${NC}"
    echo -e "  任务日志: ${CYAN}${LOG_PATH}${NC}"
    echo
  done
  [ $any -eq 0 ] && echo -e "${YELLOW}暂无任务${NC}"
}

select_task_interactive() {
  # 仅选择按任务ID命名的配置
  local confs=()
  for cf in "$TASKS_DIR"/*.conf; do
    [ -f "$cf" ] || continue
    base=$(basename "$cf")
    [[ "$base" =~ ^task_[0-9]+\.conf$ ]] && confs+=("$cf")
  done
  if [ ${#confs[@]} -eq 0 ]; then
    echo -e "${YELLOW}暂无任务${NC}"; return 1
  fi
  echo -e "${WHITE}可用任务：${NC}"
  local i=1
  for cf in "${confs[@]}"; do
    source "$cf"
    echo -e "${CYAN}${i}.${NC} ${WHITE}${TASK_NAME}${NC} (${CYAN}ID:${TASK_ID}${NC})"
    i=$((i+1))
  done
  read -p "请选择任务编号: " idx
  if ! [[ "$idx" =~ ^[0-9]+$ ]] || [ "$idx" -lt 1 ] || [ "$idx" -gt ${#confs[@]} ]; then
    echo -e "${RED}❌ 无效选择${NC}"; return 1
  fi
  SELECTED_CONF="${confs[$((idx-1))]}"
  source "$SELECTED_CONF"
  return 0
}

test_webhook_task() {
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}🧪 测试 WebHook 任务${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  select_task_interactive || return
  local gp="${PORT}"
  if [ -f "$GLOBAL_CONF" ]; then source "$GLOBAL_CONF"; gp="$GLOBAL_PORT"; fi
  local url="http://$(get_server_ip):${gp}/hooks/${TOKEN}"
  echo -e "${CYAN}触发: ${WHITE}${url}${NC}"
  curl -s -X POST "$url" >/dev/null 2>&1 && echo -e "${GREEN}✅ 已触发${NC}" || echo -e "${RED}❌ 触发失败${NC}"
  echo -e "${CYAN}带参数触发(自动URL编码):${NC}"
  echo -e "  ${WHITE}curl -s -G '${url}' --data-urlencode \"name=测试\" --data-urlencode \"age=30\" --data-urlencode \"sex=男\"${NC}"
  curl -s -G "${url}" --data-urlencode "name=测试" --data-urlencode "age=30" --data-urlencode "sex=男" >/dev/null 2>&1 \
    && echo -e "${GREEN}✅ 已触发${NC}" || echo -e "${RED}❌ 触发失败${NC}"
  sleep 1
  # 读取 token 日志优先
  local log_to_show="$LOG_PATH"
  if [ -n "$TOKEN" ]; then
    local token_log="$LOG_DIR/task_${TOKEN}.log"
    [ -f "$token_log" ] && log_to_show="$token_log"
  fi
  if [ -f "$log_to_show" ]; then
    echo -e "${YELLOW}实时全部日志：${NC}"
    cat "$log_to_show" | sed -e 's/^/  /'
  else
    echo -e "${YELLOW}暂无日志${NC}"
  fi
}

view_webhook_logs() {
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}📜 查看 WebHook 日志${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  select_task_interactive || return
  echo -e "${WHITE}1.${NC} 最近50行"
  echo -e "${WHITE}2.${NC} 实时跟随 (Ctrl+C 退出)"
  read -p "请选择 [1-2] (回车默认2): " c
  # 如果用户直接按回车，默认选择选项2
  [ -z "$c" ] && c="2"
  case "$c" in
    1)
      if [ -f "$LOG_PATH" ]; then tail -n 50 "$LOG_PATH"; else echo -e "${YELLOW}暂无日志${NC}"; fi
      ;;
    2)
      if [ -f "$LOG_PATH" ]; then 
        echo -e "${CYAN}开始实时监控日志，等待新内容... (Ctrl+C 退出)${NC}"
        tail -n 50 -f "$LOG_PATH"
      else 
        echo -e "${YELLOW}日志文件不存在，等待创建... (Ctrl+C 退出)${NC}"
        echo -e "${CYAN}正在监控: ${LOG_PATH}${NC}"
        # 使用tail -f监控文件，即使文件不存在也会等待
        tail -f "$LOG_PATH" 2>/dev/null || {
          # 如果文件不存在，创建一个空文件并监控
          touch "$LOG_PATH" 2>/dev/null
          tail -f "$LOG_PATH"
        }
      fi
      ;;
    *) echo -e "${RED}❌ 无效选择${NC}";;
  esac
}

# 查看某个任务的脚本内容（任务脚本 = 任务配置 = 多行运行脚本）
view_webhook_script() {
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}🧾 查看 WebHook 脚本${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  select_task_interactive || return
  echo -e "${WHITE}任务: ${TASK_NAME}${NC}"
  echo -e "${WHITE}脚本路径: ${CYAN}${SCRIPT_PATH}${NC}"
  if [ -f "$SCRIPT_PATH" ]; then
    echo -e "${WHITE}脚本内容：${NC}"
    echo -e "${YELLOW}----------------------------------------${NC}"
    sed -n '1,200p' "$SCRIPT_PATH" | sed -e 's/^/  /'
    echo -e "${YELLOW}----------------------------------------${NC}"
  else
    echo -e "${RED}❌ 未找到脚本文件：${SCRIPT_PATH}${NC}"
  fi
}

delete_webhook_task() {
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}🗑️ 删除 WebHook 任务${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  select_task_interactive || return
  read -p "确认删除任务 '${TASK_NAME}' ? (y/N): " ok
  [[ "$ok" =~ ^[Yy]$ ]] || { echo -e "${YELLOW}已取消${NC}"; return; }
  # 共用端口模式，无需单独停服务，直接移除配置与脚本
  if command -v systemctl >/dev/null 2>&1; then
    # 老版本每任务service清理（兼容）
    rm -f "$SYSTEMD_DIR/webhook_task_${TASK_ID}.service"
    systemctl daemon-reload 2>/dev/null || true
  fi
  rm -f "$SCRIPTS_DIR/task_${TASK_ID}_server.py" "$TASKS_DIR/task_${TASK_ID}.sh" "$TASKS_DIR/task_${TASK_ID}.conf" "$PID_DIR/task_${TASK_ID}.pid"
  echo -e "${GREEN}✅ 任务已删除${NC}"
}

# 完全卸载并重新安装
reinstall_webhook_tool() {
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}🔄 完全卸载并重新安装 WebHook 工具${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${YELLOW}⚠️  警告：此操作将删除所有任务配置和日志！${NC}"
  echo -e "${YELLOW}⚠️  包括：${NC}"
  echo -e "  • 所有WebHook任务配置"
  echo -e "  • 所有任务脚本"
  echo -e "  • 所有日志文件"
  echo -e "  • 系统服务配置"
  echo -e "  • 临时文件"
  echo
  read -p "确认完全卸载并重新安装？(y/N): " ok
  [[ "$ok" =~ ^[Yy]$ ]] || { echo -e "${YELLOW}已取消${NC}"; return; }
  
  echo -e "${YELLOW}正在停止服务...${NC}"
  # 停止服务
  if command -v systemctl >/dev/null 2>&1; then
    systemctl stop webhook_tool.service 2>/dev/null || true
    systemctl disable webhook_tool.service 2>/dev/null || true
    rm -f "$SYSTEMD_DIR/webhook_tool.service"
    systemctl daemon-reload 2>/dev/null || true
  else
    # 后台进程模式
    if [ -f "$PID_DIR/main_server.pid" ]; then
      kill $(cat "$PID_DIR/main_server.pid") 2>/dev/null || true
      rm -f "$PID_DIR/main_server.pid"
    fi
  fi
  
  echo -e "${YELLOW}正在清理所有文件...${NC}"
  # 清理所有相关文件
  rm -rf "$BASE_DIR" 2>/dev/null || true
  rm -rf "$LOG_DIR" 2>/dev/null || true
  rm -f /tmp/webhook_env_*.sh 2>/dev/null || true
  
  echo -e "${YELLOW}正在重新创建目录结构...${NC}"
  # 重新创建目录结构
  mkdir -p "$TASKS_DIR" "$SCRIPTS_DIR" "$PID_DIR" "$LOG_DIR"
  
  echo -e "${YELLOW}正在重新生成Python服务脚本...${NC}"
  # 重新生成Python服务脚本
  local global_port=$(ensure_global_port)
  create_python_server "$global_port"
  
  echo -e "${YELLOW}正在重新创建系统服务...${NC}"
  # 重新创建系统服务
  if command -v systemctl >/dev/null 2>&1; then
    create_systemd_service
  fi
  
  echo -e "${YELLOW}正在启动服务...${NC}"
  # 启动服务
  start_main_service "$global_port"
  
  echo -e "${GREEN}✅ 完全卸载并重新安装完成！${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}🎉 重新安装成功${NC}"
  echo -e "${CYAN}服务状态: ${WHITE}已启动${NC}"
  echo -e "${CYAN}服务端口: ${WHITE}${global_port}${NC}"
  echo -e "${CYAN}服务地址: ${WHITE}http://$(get_server_ip):${global_port}${NC}"
  echo -e "${CYAN}健康检查: ${WHITE}http://$(get_server_ip):${global_port}/healthz${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${YELLOW}现在可以重新创建WebHook任务了${NC}"
}

# 欢迎页（风格参考 sync_folder）
show_welcome() {
  clear
  echo -e "${CYAN}"
  echo "╔══════════════════════════════════════════════════════════════╗"
  echo "║                                                              ║"
  echo "║                 个性化 WebHook 管理器 v3.0                   ║"
  echo "║                                                              ║"
  echo "║      功能：支持多任务运行、任务维护、日志跟踪、健康检查      ║"
  echo "║      用途：将URL填入Git服务商WebHook地址，推送自动触发       ║"
  echo "║      多参：支持(URL变量参数、位置参数、Git平台数据)传参      ║"
  echo "║      触发：脚本内容支持单行命令、多行脚本命令自定义执行      ║"
  echo "║                                                              ║"
  echo "║                       作者：宋利军                           ║"
  echo "║                        版本：v3.0                            ║"
  echo "╚══════════════════════════════════════════════════════════════╝"
  echo -e "${NC}"
}

show_main_menu() {
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${WHITE}请选择操作：${NC}"
  echo -e "${CYAN}1.${NC} ${WHITE}新增 WebHook 任务${NC}"
  echo -e "${CYAN}2.${NC} ${WHITE}查看 WebHook 列表${NC}"
  echo -e "${CYAN}3.${NC} ${WHITE}查看 WebHook 脚本${NC}"
  echo -e "${CYAN}4.${NC} ${WHITE}测试 WebHook 任务${NC}"
  echo -e "${CYAN}5.${NC} ${WHITE}查看 WebHook 日志${NC}"
  echo -e "${CYAN}6.${NC} ${WHITE}删除 WebHook 任务${NC}"
  echo -e "${CYAN}7.${NC} ${WHITE}重装 WebHook 服务${NC}"
  echo -e "${CYAN}8.${NC} ${WHITE}退出${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}

main() {
  check_dependencies
  while true; do
    show_welcome
    show_main_menu
    read -p "请选择 [1-8]: " choice
    case "$choice" in
      1) add_webhook_task ;;
      2) list_webhook_tasks ;;
      3) view_webhook_script ;;
      4) test_webhook_task ;;
      5) view_webhook_logs ;;
      6) delete_webhook_task ;;
      7) reinstall_webhook_tool ;;
      8) echo -e "${GREEN}感谢使用 WebHook 管理器！${NC}"; exit 0 ;;
      *) echo -e "${RED}❌ 无效选择${NC}" ;;
    esac
    echo -e "\n${YELLOW}按回车键继续...${NC}"; read
  done
}

main "$@"
